package com.macro.mall.demo.service.impl;


import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageInfo;
import com.macro.mall.common.es.EsAddDocDto;
import com.macro.mall.common.es.EsDelDocDto;
import com.macro.mall.demo.service.EsIndexService;
import com.macro.mall.demo.service.EsQueryService;
import com.macro.mall.model.EsQueryAndPage;
import com.macro.mall.model.MatchQueryInfo;
import com.macro.mall.model.OrderInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.DocWriteResponse;
import org.elasticsearch.action.bulk.BulkItemResponse;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @Author：hwh
 * @Description:
 * @name：EsDocServiceImpl
 * @Date：2024/10/16 17:00
 */
@Service
@Slf4j

public class EsQueryServiceImpl implements EsQueryService {

    @Autowired
    private RestHighLevelClient client;

    @Autowired
    private EsIndexService esIndexService;


    /**
     * 批量添加
     *
     * @param esAddDocDto 包含索引名、文档数据及主键字段
     */
    @Override
    public void batchSave(EsAddDocDto esAddDocDto) {
        // 验证索引是否存在
        String indexName = esAddDocDto.getIndexName();
        if (!esIndexService.indexExists(indexName)) {
            String errorMessage = "索引不存在，请创建: " + indexName;
            log.error(errorMessage);
            throw new RuntimeException(errorMessage);
        }
        log.info("开始批量添加文档到索引: {}", indexName);

        // 获取文档数据和主键字段
        List<HashMap<String, Object>> datas = esAddDocDto.getDatas();
        String idField = esAddDocDto.getIdField();

        // 检查数据是否为空
        if (datas == null || datas.isEmpty()) {
            String errorMessage = "文档数据不能为空";
            log.error(errorMessage);
            throw new IllegalArgumentException(errorMessage);
        }

        log.info("待添加的文档数量: {}", datas.size());
        try {
            // 创建 BulkRequest
            BulkRequest bulkRequest = new BulkRequest();

            // 遍历文档数据，创建 IndexRequest 并添加到 BulkRequest
            for (HashMap<String, Object> data : datas) {
                // 检查主键是否为空
                String idValue = StringUtils.isNotBlank(idField) ? data.get(idField).toString() : null;
                if (StringUtils.isBlank(idValue)) {
                    log.warn("数据中缺少主键字段: {}，将使用自动生成的 ID", idField);
                } else {
                    log.debug("为文档设置主键 ID: {}", idValue);
                }

                // 创建 IndexRequest 并设置文档 ID 和内容
                IndexRequest indexRequest = new IndexRequest(indexName)
                        .id(idValue) // 设置文档 ID（可为 null）
                        .source(data, XContentType.JSON);
                bulkRequest.add(indexRequest);
            }

            // 执行批量请求
            BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);
            log.info("成功执行批量请求，处理文档数量: {}", bulkResponse.getItems().length);

            // 处理批量响应
            handleBulkResponse(bulkResponse, indexName);

        } catch (Exception e) {
            log.error("批量添加文档时发生错误: {}", e.getMessage(), e);
            throw new RuntimeException("批量添加文档时发生错误: " + e.getMessage(), e);
        }
    }


    /**
     * 处理批量响应
     *
     * @param bulkResponse 批量响应对象
     * @param indexName    索引名称
     */
    private void handleBulkResponse(BulkResponse bulkResponse, String indexName) {
        // 检查批量响应是否有失败
        if (bulkResponse.hasFailures()) {
            // 处理响应中的所有项目
            BulkItemResponse[] items = bulkResponse.getItems();

            // 准备删除操作的文档 ID 列表
            EsDelDocDto esDelDocDto = new EsDelDocDto();
            List<String> docIds = new ArrayList<>();
            esDelDocDto.setDocId(docIds);
            esDelDocDto.setIndexName(indexName);

            // 遍历每个响应项目，收集成功添加的文档 ID
            for (BulkItemResponse item : items) {
                if (item.getResponse().getResult() == DocWriteResponse.Result.CREATED) {
                    docIds.add(item.getId());
                    log.debug("文档添加成功，ID: {}", item.getId());
                } else {
                    log.warn("文档添加失败，ID: {}, 原因: {}", item.getId(), item.getFailureMessage());
                }
            }

            // 记录失败的批量添加信息
            String failureMessage = bulkResponse.buildFailureMessage();
            log.error("批量添加失败: {}", failureMessage);

            // 删除已成功添加的文档
            if (!docIds.isEmpty()) {
                log.info("删除已添加的文档: {}", docIds);
                deleteEsDoc(esDelDocDto);
            }

            throw new RuntimeException("批量添加数据未成功，请查看日志获取详细信息");
        } else {
            // 记录成功信息
            log.info("批量添加成功，添加数量: {}", bulkResponse.getItems().length);
        }
    }


    /**
     * 批量删除文档
     *
     * @param esDelDocDto 包含索引名称和文档 ID 列表的 DTO
     */
    @Override
    public void deleteEsDoc(EsDelDocDto esDelDocDto) {
        String indexName = esDelDocDto.getIndexName();
        List<String> docIds = esDelDocDto.getDocId();

        // 检查索引是否存在
        if (!esIndexService.indexExists(indexName)) {
            log.error("索引不存在: {}", indexName);
            throw new RuntimeException("索引不存在, 请创建");
        }

        // 创建一个 BulkRequest 用于批量删除
        BulkRequest bulkRequest = new BulkRequest();

        // 遍历文档 ID，创建删除请求
        for (String docId : docIds) {
            DeleteRequest deleteRequest = new DeleteRequest(indexName, docId);
            bulkRequest.add(deleteRequest); // 将删除请求添加到批量请求中
            log.debug("添加删除请求，索引: {}, 文档ID: {}", indexName, docId);
        }

        // 执行批量请求
        try {
            BulkResponse bulkResponse = client.bulk(bulkRequest, RequestOptions.DEFAULT);

            // 检查批量响应是否有失败
            if (bulkResponse.hasFailures()) {
                log.error("批量删除失败: {}", bulkResponse.buildFailureMessage());
            } else {
                log.info("成功从索引 '{}' 删除了 {} 个文档", indexName, docIds.size());
            }
        } catch (IOException e) {
            // 适当地处理异常
            log.error("删除文档时发生错误: {}", e.getMessage(), e);
        }
    }


    /**
     * 查询
     *
     * @param esQueryAndPage 查询条件和分页信息
     * @return 查询结果列表
     */
    @Override
    public List<Object> query(EsQueryAndPage esQueryAndPage) {
        String indexName = esQueryAndPage.getIndexName();

        // 判断索引是否存在
        if (!esIndexService.indexExists(indexName)) {
            log.error("索引不存在: {}", indexName);
            throw new RuntimeException("索引不存在, 请创建");
        }

        List<Object> list = new ArrayList<>();

        // 分页信息
         PageInfo pageInfo = esQueryAndPage.getPageInfo();
        Integer pageNum = pageInfo.getPageNum();
        Integer pageSize = pageInfo.getPageSize();
        log.debug("分页信息 - 页码: {}, 每页大小: {}", pageNum, pageSize);

        // 排序信息
        OrderInfo orderInfo = esQueryAndPage.getOrderInfo();
        String orderField = orderInfo.getOrderField();
        String orderType = orderInfo.getOrderType();
        log.debug("排序信息 - 排序字段: {}, 排序类型: {}", orderField, orderType);

        // 查询条件
        MatchQueryInfo matchQueryInfo = esQueryAndPage.getMatchQueryInfo();
        String keyWord = matchQueryInfo.getKeyWord();
        List<String> searchFields = matchQueryInfo.getSearchFields();
        log.debug("查询条件 - 关键字: {}, 搜索字段: {}", keyWord, searchFields);

        // 创建搜索请求
        SearchRequest searchRequest = new SearchRequest(indexName);
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        // 添加搜索条件
        if (StringUtils.isNotBlank(keyWord) && !searchFields.isEmpty()) {
            searchFields.forEach(searchField -> {
                boolQueryBuilder.should(QueryBuilders.matchQuery(searchField, keyWord));
                log.debug("添加查询条件 - 字段: {}, 关键字: {}", searchField, keyWord);
            });
        }

        // 设置排序
        if (StringUtils.isNotBlank(orderField) && StringUtils.isNotBlank(orderType)) {
            searchSourceBuilder.sort(orderField, SortOrder.valueOf(orderType));
            log.debug("设置排序 - 字段: {}, 类型: {}", orderField, orderType);
        }

        // 设置分页
        if (pageNum != null && pageSize != null) {
            searchSourceBuilder.from((pageNum - 1) * pageSize);
            searchSourceBuilder.size(pageSize);
            log.debug("设置分页 - 从: {}, 大小: {}", (pageNum - 1) * pageSize, pageSize);
        }

        // 设置查询条件
        searchSourceBuilder.query(boolQueryBuilder);
        searchRequest.source(searchSourceBuilder);
        log.info("发送查询请求到索引: {}", indexName);

        try {
            // 执行查询
            SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
            SearchHits hits = searchResponse.getHits();
            SearchHit[] searchHits = hits.getHits();

            // 处理查询结果
            for (SearchHit searchHit : searchHits) {
                JSONObject jsonObject = JSON.parseObject(searchHit.getSourceAsString());
                list.add(jsonObject);
            }

            log.info("查询成功 - 返回结果数量: {}", list.size());
            return list;
        } catch (IOException e) {
            log.error("查询索引 '{}' 时发生错误: {}", indexName, e.getMessage(), e);
            throw new RuntimeException("查询失败", e);
        }
    }


}
